﻿using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
using System;
using System.Collections.Generic;
using System.Numerics;
using Windows.Foundation;
using Windows.Graphics.Effects;
using Windows.UI;

namespace TestApp2.Historys
{
    [Flags]
    public enum RegionType : uint
    {
        None = 0, // 0000
        XRemainder = 1, // 0001
        YRemainder = 2, // 0010
        XYRemainder = 3, // 0011
    }

    public class BitmapLayer
    {
        public IGraphicsEffectSource Source => this.RenderTarget;
        readonly CanvasRenderTarget OriginRenderTarget;
        readonly CanvasRenderTarget RenderTarget;

        /// <summary>
        /// (100, 100) (100, 100) (050, 100) 
        /// (100, 100) (100, 100) (050, 100) 
        /// (100, 050) (100, 050) (050, 050) 
        /// </summary>
        public const int Unit = 100;

        public readonly RegionType Type; // XYRemainder
        public readonly int Width; // 250
        public readonly int Height; // 250
        public readonly int XDivisor; // 2
        public readonly int YDivisor; // 2
        public readonly int XRemainder; // 50
        public readonly int YRemainder; // 50

        public readonly int XLength; // 3
        public readonly int YLength; // 3
        public readonly bool[] Hits; // [9*9]

        public BitmapLayer(ICanvasResourceCreator resourceCreator, int w, int h)
        {
            this.OriginRenderTarget = new CanvasRenderTarget(resourceCreator, w, h, 96);
            this.RenderTarget = new CanvasRenderTarget(resourceCreator, w, h, 96);
            this.Reset();

            this.Width = w;
            this.Height = h;
            this.XDivisor = w / BitmapLayer.Unit;
            this.YDivisor = h / BitmapLayer.Unit;
            this.XRemainder = w % BitmapLayer.Unit;
            this.YRemainder = h % BitmapLayer.Unit;

            bool noXR = this.XRemainder == 0;
            bool noYR = this.YRemainder == 0;

            if (noXR && noYR)
            {
                this.Type = RegionType.None;
                this.XLength = this.XDivisor;
                this.YLength = this.YDivisor;
            }
            else if (noXR && noYR == false)
            {
                this.Type = RegionType.XYRemainder;
                this.XLength = this.XDivisor;
                this.YLength = this.YDivisor + 1;
            }
            else if (noXR == false && noYR)
            {
                this.Type = RegionType.YRemainder;
                this.XLength = this.XDivisor + 1;
                this.YLength = this.YDivisor;
            }
            else
            {
                this.Type = RegionType.XYRemainder;
                this.XLength = this.XDivisor + 1;
                this.YLength = this.YDivisor + 1;
            }

            this.Hits = new bool[this.XLength * this.YLength];
        }


        public float GetRadius(float rangeSize) => rangeSize / 2f / Math.Max(this.Width, this.Height);

        public void Render(Vector2 position, Vector2 targetPosition, float size, float pressure, Rect rect)
        {
            float length = Vector2.Distance(position, targetPosition);
            if (length < size / 2)
            {
                using (CanvasDrawingSession ds = this.RenderTarget.CreateDrawingSession())
                {
                    ds.FillCircle(position, size, Colors.Black);
                }
            }
            else
            {
                float spane = size / 2 / length;
                using (CanvasDrawingSession ds = this.RenderTarget.CreateDrawingSession())
                {
                    ds.FillCircle(position, size, Colors.Black);
                    for (float i = spane; i < 1f; i += spane)
                    {
                        var p = position * i + (1 - i) * targetPosition;
                        ds.FillCircle(p, size, Colors.Black);
                    }
                }
            }
        }


        public void Reset()
        {
            using (CanvasDrawingSession ds = this.OriginRenderTarget.CreateDrawingSession())
            {
                ds.Clear(Colors.Transparent);
            }
            using (CanvasDrawingSession ds = this.RenderTarget.CreateDrawingSession())
            {
                ds.Clear(Colors.Transparent);
            }
        }

        public void Flush()
        {
            using (CanvasDrawingSession ds = this.OriginRenderTarget.CreateDrawingSession())
            {
                ds.DrawImage(this.RenderTarget);
            }
        }


        public HistoryBase GetHistory()
        {
            IDictionary<int, Color[]> undo = new Dictionary<int, Color[]>();
            IDictionary<int, Color[]> redo = new Dictionary<int, Color[]>();

            for (int i = 0; i < this.Hits.Length; i++)
            {
                if (this.Hits[i] == false) continue;
                this.Hits[i] = false;

                undo.Add(i, this.GetPixelColors(this.OriginRenderTarget, i));
                redo.Add(i, this.GetPixelColors(this.RenderTarget, i));
            }

            return new BitmapHistory(undo, redo);
        }
        public bool UndoHistory(HistoryBase history)
        {
            if (history is BitmapHistory bitmapHistory)
            {
                if (bitmapHistory.UndoParameter is IDictionary<int, Color[]> colors)
                {
                    this.SetPixelColors(colors);
                    return true;
                }
            }

            return false;
        }
        public bool RedoHistory(HistoryBase history)
        {
            if (history is BitmapHistory bitmapHistory)
            {
                if (bitmapHistory.RedoParameter is IDictionary<int, Color[]> colors)
                {
                    this.SetPixelColors(colors);
                    return true;
                }
            }

            return false;
        }


        public void Hit(Rect rect)
        {
            if (rect.Left > this.Width) return;
            if (rect.Top > this.Height) return;
            if (rect.Right < 0) return;
            if (rect.Bottom < 0) return;

            int left = Math.Max(0, (int)rect.Left / BitmapLayer.Unit);
            int top = Math.Max(0, (int)rect.Top / BitmapLayer.Unit);
            int right = Math.Min(this.XLength, (int)Math.Ceiling(rect.Right / BitmapLayer.Unit));
            int bottom = Math.Min(this.YLength, (int)Math.Ceiling(rect.Bottom / BitmapLayer.Unit));
            for (int x = left; x < right; x++)
            {
                for (int y = top; y < bottom; y++)
                {
                    this.Hits[x + this.XLength * y] = true;
                }
            }
        }


        public Color[] GetPixelColors(CanvasBitmap bitmap, int hitIndex)
        {
            int x = hitIndex % this.XLength;
            int y = hitIndex / this.XLength;
            return bitmap.GetPixelColors
            (
                x * BitmapLayer.Unit,
                y * BitmapLayer.Unit,
                x == this.XDivisor ? this.XRemainder : BitmapLayer.Unit,
                y == this.YDivisor ? this.YRemainder : BitmapLayer.Unit
            );
        }

        public void SetPixelColors(IDictionary<int, Color[]> colors)
        {
            foreach (var item in colors)
            {
                this.SetPixelColors(this.RenderTarget, item.Value, item.Key);
            }
        }

        private void SetPixelColors(CanvasBitmap bitmap, Color[] valueElements, int hitIndex)
        {
            int x = hitIndex % this.XLength;
            int y = hitIndex / this.XLength;
            bitmap.SetPixelColors
            (
                valueElements,
                x * BitmapLayer.Unit,
                y * BitmapLayer.Unit,
                x == this.XDivisor ? this.XRemainder : BitmapLayer.Unit,
                y == this.YDivisor ? this.YRemainder : BitmapLayer.Unit
            );
        }

    }
}